Lurch Deductive Engine

Tutorial: Free and bound variables

Free and bound variables

This tutorial assumes you know how to construct LC instances; if not, see the tutorial on Constructing LCs. This tutorial covers what it means for a symbol to be free or bound inside an LC tree.

(Each piece of sample code below is written as if it were a script sitting in the root folder of this source code repository, and run from there with the command-line tools node. If you place your scripts in another folder, you will need to adjust the path in each import statement accordingly. If you have not yet set up a copy of this repository with the appropriate Node.js version installed, see our GitHub README, which explains how to do so.)

Free variables vs. free symbols

Most situations that deal with the concepts of free/bound with respect to binding expressions speak of free and bound variables. But in the world of LCs, we do not have variables as a specific category; we have only the general category of symbols that can be used as variables or constants. Thus here we will speak of free and bound symbols.

The concept of a bound variable has the usual meaning: Inside any Binding expression (head sym1 sym2 ... symN , body), the symbols sym1 through symN are bound. Outside of that expression (assuming there is no containing binding expression of the same symbol(s)), they are not bound, and are thus called free.



import { LogicConcept } from './src/index.js'

const binding = LogicConcept.fromPutdown( '(forall x , (P x))' )[0]
binding.descendantsSatisfying( d => d.isAtomic() ).map( d => {
    console.log( 'Is', d.toPutdown(), 'free?', d.isFree() )
} )
// Console output:
Is forall free? true
Is x free? false
Is P free? true
Is x free? false


You can also ask the question relatively: Is a symbol free in a specific ancestor?



const outerBinding = LogicConcept.fromPutdown( '(exists x , (forall y , (> x y)))' )[0]
const innerBinding = outerBinding.body()
const secondX = innerBinding.body().child( 1 )
console.log( 'Is x free in the inner binding?', secondX.isFree( innerBinding ) )
console.log( 'Is x free in the outer binding?', secondX.isFree( outerBinding ) )
// Console output:
file:///Users/monks/Dropbox/files/monkware/git/lde/tutorial-tmp-file-Free%20and%20bound%20variables.md.js:9
const innerBinding = outerBinding.body()
                                  ^

TypeError: outerBinding.body is not a function
    at file:///Users/monks/Dropbox/files/monkware/git/lde/tutorial-tmp-file-Free%20and%20bound%20variables.md.js:9:35
    at ModuleJob.run (node:internal/modules/esm/module_job:198:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:409:24)
    at async loadESM (node:internal/process/esm_loader:85:5)
    at async handleMainPromise (node:internal/modules/run_main:61:12)

Node.js v18.4.0


The isFree() method works not only for symbols. You can ask whether a nonatomic expression is free, and it will be free if and only if all of the symbols free within it are free in the given context.

console.log( innerBinding.body().isFree( innerBinding ) )

Free occurrences

We can also ask whether a symbol occurs free anywhere in an expression. Note the difference between x.occursFree(y) and y.occursFree(x) and that the order may be different from what you expect.

import { LurchSymbol } from './src/index.js'

const example = LogicConcept.fromPutdown( '(and (> x 1) (forall x , (R x x)))' )[0]
console.log( 'Last x free?', example.child( 2 ).body().child( 2 ).isFree() )
console.log( 'Any x free?', example.occursFree( new LurchSymbol( 'x' ) ) )

You can get all free occurrences by combining descendantsSatisfying() with isFree().

Variable capture and replacing free occurrences

The usual notion of variable capture exists when considering substitution into a binding, and you can check to see if it will happen using isFreeToReplace(). You can do all of the free replacements with replaceFree().

console.log( 'Before:', example.toPutdown() )
example.replaceFree(
    new LurchSymbol( 'x' ),
    LogicConcept.fromPutdown( '(- 3 x)' )[0] )
console.log( 'After:', example.toPutdown() )